/*
*
==============================================================================
*  Name        : 
*  Part of     : Omen Game Engine
*  Description : 
*  Version     : 1.0
*
*  Copyright (c) 2013 Lauri Kortevaara.
* 
==============================================================================
*/

#define OMEN_SOURCE 1

/// Omen Headers
#include <OmenXmlReader.h>

/// STL Headers
#include <fstream>
#include <iostream>
#include <iterator>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <windows.h>

using namespace Omen;

// function for trimming the string ends
static void trimLineEnds( std::wstring& str );
static void convertTabsToSpaces( std::wstring& str );

/// <summary></summary>
XmlReader::XmlReader( const std::wstring& filename )
{
    char path[256];
    ::GetCurrentDirectoryA( 256, path );
    std::wifstream fin;
    fin.open(filename);
    if( fin.is_open() )
    {
        // Find file size
        std::wifstream::pos_type begin = fin.tellg();
        fin.seekg (0, std::ios::end);
        std::wifstream::pos_type end = fin.tellg();

        // Rewind to the beginning of the file
        fin.seekg(0);

        bool bCommentSection = false;

        // read the file line by line
        while( fin.good() )
        {
            std::wstring line;
            getline( fin, line );

            // Remove spaces from the beginning and the end of the line
            convertTabsToSpaces(line);
            trimLineEnds(line);
            
            // Remove comment sections
            std::size_t comment_start = line.find( L"<!--" );
            std::size_t comment_end = line.find( L"-->" ) != std::wstring::npos ? line.find( L"-->" )+3 : std::wstring::npos;

            if( comment_start != std::string::npos )
                bCommentSection = true;

            if( comment_end != std::wstring::npos )
                bCommentSection = false;

            if( comment_start != std::string::npos )
            {
                if( comment_end != std::string::npos )
                    line.erase( comment_start, comment_end-comment_start );
                else
                    line.erase( comment_start, line.back() );
            }

            // If the whole line is in comment section skip the line completely
            if( !bCommentSection && !line.empty() )
                m_buffer.push_back( line );
        }
    }
}

/// <summary>Destructor frees the xml document buffer</summary>
XmlReader::~XmlReader()
{
}

/// <summary>parseHeader will call parseDocument(true), which parses only the header</summary>
bool XmlReader::parseHeader()
{
    return parseDocument(true);
}

/// <summary>parseDocument parses the xml-file and creates the DOM from the contents</summary>
/// <param>bReadOnlyHeader[IN, default:false]: true: reads only the first tag(header) from the file. false: reads the whole document</param>
bool XmlReader::parseDocument( bool bReadOnlyHeader )
{
    bool bIsXmlFormat = false;
    auto bufIter = m_buffer.begin();

    // Clear the current DOM if the file has been parsed already
    m_document.clear();

    while( bufIter != m_buffer.end() )
    {
        // Skip empty lines
        if( (*bufIter).empty() || (*bufIter).find_first_not_of(L" ") == std::wstring::npos )
        {
            ++bufIter;
            continue;
        }
        std::wstring data = *bufIter;
        
        /// trim the ends
        data = data.substr( data.find_first_not_of(L" "), data.find_last_not_of(L" ")+1);
        
        // New node starts
        if( data.find(L'<') != std::wstring::npos )
        {
            auto iTagStart = data.begin();
            auto iTagEnd = data.begin();

            while( true )
            {
                if( iTagEnd == data.end() || (*iTagEnd == L'>') )
                    break;
                ++iTagEnd;
            }
            if( iTagEnd == data.end() )
                throw std::runtime_error( "Error in XmlReader document! Unending tag" );
            
            std::wstring wstrTag;
            std::wstringstream strStream(wstrTag);
            do
            {
                wchar_t wchar = *iTagStart;
                strStream << wchar;
            }while ( iTagStart++ != iTagEnd );
            wstrTag = strStream.str();

            std::wcout << L"Found Xml tag: " << wstrTag << std::endl;
            if( !wstrTag.empty() )
            {
                bIsXmlFormat = true;
                std::wstring nodeName = wstrTag.substr( wstrTag.find_first_not_of(L"< ") );
                nodeName = nodeName.substr( 0, nodeName.find_first_of( L"> " ) );

                // Create new node
                XmlNode node(nodeName, m_document);
                parseAttributes( node, wstrTag );
                // If we're about to read the whole file
                if( !bReadOnlyHeader )
                    parseNode( node, ++bufIter );
                m_document.addRootNode( node );
            }
        }
        if( bufIter < m_buffer.end() )
            ++bufIter;
    }
    return bIsXmlFormat;
}

void XmlReader::parseAttributes( XmlNode& node, const std::wstring& strnode )
{
    // Check if there are any attributes/paramters
    std::wstring attribs = strnode;
    while( attribs.find( L"=" ) != std::wstring::npos )
    {
        int first_eql_pos = attribs.find( L"=" );
        int key_start = attribs.rfind( L" ", first_eql_pos );
        if( key_start == std::wstring::npos )
            key_start = 0;

        int value_start = attribs.find_first_of( L"\"", first_eql_pos );

        int value_end = attribs.find_first_of( L"\"", value_start+1 );

        if( value_end == std::wstring::npos )
            value_end = attribs.length()-1;
                    
        std::wstring key = attribs.substr( key_start, first_eql_pos-key_start );
        std::wstring value = attribs.substr( value_start+1, value_end-value_start-1 );
        
        trimLineEnds( key );
        trimLineEnds( value );

        // Remove the quotes
        while( key.find( L"\"" ) != std::wstring::npos )
            key.erase( key.find( L"\"" ), 1 );

        while( value.find( L"\"" ) != std::wstring::npos )
            value.erase( value.find( L"\"" ), 1 );

        node.addAttribute( XmlNodeAttribute(key, value) );
        attribs = attribs.substr( value_end+1 );
    }
}

/// <summary>Remove spaces from the end and the beginning of the given string</summary>
static void trimLineEnds( std::wstring& str )
{
    if( !str.empty() )
    {
        std::size_t pos = str.find_first_not_of( L" " );
        if( pos != std::wstring::npos )
            str.erase( 0, pos );
        else
            str.clear();

        pos = str.find_last_not_of( L" " );
        if( pos != std::wstring::npos )
            str.erase( pos+1, str.back() );
    }
}

/// <summary>Remove spaces from the end and the beginning of the given string</summary>
static void convertTabsToSpaces( std::wstring& str )
{
    std::replace( str.begin(), str.end(), '\t', ' '); 
}

/// <summary></summary>
bool XmlReader::parseNode( XmlNode& node, std::vector<std::wstring>::iterator& bufIter, std::wstring& data_str )
{
    while( bufIter != m_buffer.end() )
    {
        std::wstring data;
        
        // If we continue parsin the data from the same line as the node started
        if( !data_str.empty() )
            data = data_str;
        else
            data = *bufIter;

        // Skip empty lines
        if( data.empty() || data.find_first_not_of(L" ") == std::wstring::npos )
        {
            ++bufIter;
            continue;
        }

        // Trim the begin and the end
        trimLineEnds(data);

        if( !data.empty() )
        {
            // Check if current node ends
            std::size_t tagStart = data.find_first_of( L"<" );
            std::size_t tagEnd = data.find_first_of( L">" );
            
            std::size_t endTagStart = data.find( L"</" );

            if( endTagStart != std::wstring::npos && endTagStart < tagEnd ) /// Continue writing the xml-tag ending...
            {
                std::wstring nodeData = data.substr( 0, data.find( L"</" ) );
                if( !nodeData.empty() && nodeData.find_first_not_of( L" " ) != std::wstring::npos )
                    node.data().append(nodeData);
                // End parsing this node
                return true;
            }
            // New node starts
            if( data.find( L'<' ) != std::wstring::npos )
            {
                std::size_t iTagStart = data.find_first_not_of( L"< " );
                std::size_t iTagEnd = data.find_first_of( L"> ", iTagStart );

                std::wstring tagName = data.substr( iTagStart, iTagEnd-1 );

                std::wcout << L"Found Xml tag: " << tagName << std::endl;
                if( !tagName.empty() )
                {
                    // Create new node
                    XmlNode childNode(tagName, node);
                
                    // Parse and add attributes
                    parseAttributes( childNode, data );

                    // Check if the node ends already on the same line
                    if( data.find( L"</" ) == std::wstring::npos && data.find( L"/>" ) == std::wstring::npos )
                    {
                        std::wstring strData;
                        ++bufIter;
                        parseNode( childNode, bufIter, strData );
                    }
                    else
                    {
                        std::size_t data_start = data.find_first_of( L">" );
                        if( data_start != std::wstring::npos )
                        {
                            std::size_t data_end = data.find_first_of( L"<", data_start );
                            if( data_end != std::wstring::npos )
                            {
                                std::wstring nodeData = data.substr( data_start+1, data_end-data_start-1 );
                                childNode.setData(nodeData);
                            }
                        }
                    }
                    node.addChild(childNode);
                }
            }
        }
        if( bufIter != m_buffer.end() )
            ++bufIter;
    }
    return true;
}

/// <summary>Returns true if the document was returned in the document parameter, false otherwise</summary>
bool XmlReader::getDocument( XmlDocument& document )
{
    document = m_document;
    return true;
}    

/// <summary></summary>
XmlDocument::XmlDocument()
{
}

/// <summary></summary>
XmlDocument::XmlDocument( const XmlDocument& other )
{
    m_rootNodes = other.m_rootNodes;
}

/// <summary></summary>
XmlDocument::~XmlDocument()
{
}

/// <summary>Clears the DOM</summary>
void XmlDocument::clear()
{
    m_rootNodes.clear();
}

/// <summary>Adds the given node as a rootnode</summary>
void XmlDocument::addRootNode( XmlNode& node )
{
    m_rootNodes.push_back( node );
}

/// <summary>Returns the first root node in the document</summary>
std::vector<XmlNode>::iterator XmlDocument::begin()
{
    return this->m_rootNodes.begin();
}    


/// <summary>Returns the las node in the document</summary>
std::vector<XmlNode>::iterator XmlDocument::end()
{
    return this->m_rootNodes.end();
}    
    
/// <summary></summary>
XmlNode::XmlNode( const std::wstring& nodeName, XmlDocument& doc ) 
    :
    m_nodeName(nodeName),
    m_document(doc),
    m_pParent(NULL) // the parent node is a root Node
{
    m_document = doc;
}

/// <summary></summary>
XmlNode::XmlNode( const std::wstring& nodeName, XmlNode& parent ) 
    : 
    m_nodeName(nodeName),
    m_pParent(&parent), 
    m_document(parent.document())
{
}

/// <summary></summary>
XmlNode::XmlNode( const XmlNode& other ) 
    : 
        m_pParent(other.m_pParent),
        m_document(other.m_document),
        m_nodeName(other.m_nodeName ),
        m_listChildren( other.m_listChildren ),
        m_attributes( other.m_attributes ),
        m_data( other.m_data )
{
}

/// <summary></summary>
XmlNode::~XmlNode()
{
}

void XmlNode::addChild( XmlNode node )
{
    m_listChildren.push_back(node);
}

void XmlNode::setData( const std::wstring& data )
{
    m_data = data;
}

void XmlNode::setName( const std::wstring& name )
{
    m_nodeName = name;
}

/// <summary></summary>
XmlNode*    XmlNode::parent()
{
    return m_pParent;
}

/// <summary>Returns a reference to the node-data</summary>
std::wstring& XmlNode::data()
{
    return m_data;
}

/// <summary>Returns a reference to the node-attributes</summary>
std::vector<XmlNodeAttribute>& XmlNode::attributes()
{
    return m_attributes;
}

/// <summary>Returns a reference to the node-attribute matching with given name</summary>
/// <param>attributeName [IN]: name of the attribute</param>
/// <param>attribute[OUT]: found attribute is returned in this variable</param>
/// <returns>true if attribute was found, false otherwise</returns>
bool XmlNode::getAttribute( const std::wstring& attributeName, XmlNodeAttribute& attribute )
{
    auto iter = std::find( m_attributes.begin(), m_attributes.end(), attributeName );
    if( iter != m_attributes.end() )
    {
        attribute = *iter;
        return true;
    }
    return false;
}

std::wstring& XmlNode::name()
{
    return m_nodeName;
}

/// <summary></summary>
XmlDocument& XmlNode::document()
{
    return m_document;
}


/// <summary></summary>
std::vector<XmlNode>&  XmlNode::children()
{
    return m_listChildren;
}

XmlNode& XmlNode::operator=( const XmlNode& other )
{
    m_pParent = other.m_pParent;
    m_document = other.m_document;

    m_nodeName = other.m_nodeName;
    m_listChildren = other.m_listChildren;
    
    m_attributes = other.m_attributes;
    m_comment = other.m_comment;
    
    return *this;
}

void XmlNode::addAttribute( XmlNodeAttribute& attribute )
{
    m_attributes.push_back( attribute );
}

/// <summary>Default constructor</summary>
XmlNodeAttribute::XmlNodeAttribute() 
{
}

/// <summary></summary>
XmlNodeAttribute::XmlNodeAttribute( const std::wstring& attributeName, const std::wstring& attributeValue ) 
    : m_name(attributeName), 
      m_value(attributeValue)
{
}

/// <summary></summary>
std::wstring& XmlNodeAttribute::name()
{
    return m_name;
}

/// <summary></summary>
std::wstring& XmlNodeAttribute::value()
{
    return m_value;
}

/// <summary>Attribute name compare operator</summary>
bool XmlNodeAttribute::operator==( const std::wstring& name )
{
    if( m_name.compare(name) == 0 )
        return true;
    else
        return false;
}

/// <summary>Attribute name compare operator</summary>
bool XmlNodeAttribute::operator==( const XmlNodeAttribute& other )
{
    if( m_name.compare(other.m_name) == 0 )
        return true;
    else
        return false;
}

/// <summary>Attribute name compare operator</summary>
bool XmlNodeAttribute::operator<( const XmlNodeAttribute& other )
{
    return !m_name.compare(other.m_name);
}

/// EOF